//------------------------------------------------------------
// * Author         : Ekko
// * Description    : ListView
//------------------------------------------------------------

type ListViewParams = {
    scrollview: cc.ScrollView;
    mask: cc.Node;
    content: cc.Node;
    item_tpl: cc.Node;
    direction?: ListViewDir;
    width?: number;
    height?: number;
    gapX?: number;
    gapY?: number;
    row?: number;                                                                   // 水平方向排版时，垂直方向上的行数
    column?: number;                                                                // 垂直方向排版时，水平方向上的列数
    cb_host?: any;                                                                  // 回调函数host
    item_setter: (item: cc.Node, data: any, index: number) => void;                 // item更新setter
    recycle_cb?: (item: cc.Node) => void;                                           // 回收时的回调
    select_cb?: (data: any, index: number) => void;                                 // item选中回调
    select_setter?: (item: cc.Node, is_select: boolean, index: number) => void;     // item选中效果setter
    scroll_to_end_cb?: () => void;                                                  // 滚动到尽头的回调
    auto_scrolling?: boolean;                                                       // append时自动滚动到尽头
}

export enum ListViewDir{
    Vertical = 1,
    Horizontal = 2,
}

type ListItem = {
    x: number;
    y: number;
    data: any;
    node: cc.Node;
    is_select: boolean;
}

export class ListView{
    private scrollview: cc.ScrollView;
    private mask: cc.Node;
    private content: cc.Node;
    private item_tpl: cc.Node;
    private node_pool: cc.Node[];

    private dir: number;
    private width: number;
    private height: number;
    private gapX: number;
    private gapY: number;
    private row: number;
    private col: number;
    private item_width: number;
    private item_height: number;
    private cb_host: any;
    private item_setter: (item: cc.Node, data: any, index: number) => void;
    private recycle_cb: (item: cc.Node) => void;
    private select_cb: (data: any, index: number) => void;
    private select_setter: (item: cc.Node, is_select: boolean, index: number) => void;
    private scroll_to_end_cb: () => void;
    private auto_scrolling: boolean;
    private items: ListItem[];
    private start_index: number;
    private stop_index: number;
    private _datas: any[];
    private _select_index: number = -1;

    constructor(params: ListViewParams){
        this.scrollview = params.scrollview;
        this.mask = params.mask;
        this.content = params.content;
        this.item_tpl = params.item_tpl;
        this.item_tpl.active = false;
        this.item_width = this.item_tpl.width;
        this.item_height = this.item_tpl.height;
        this.dir = params.direction || ListViewDir.Vertical;
        this.width = params.width || this.mask.width;
        this.height = params.height || this.mask.height;
        this.gapX = params.gapX || 0;
        this.gapY = params.gapY || 0;
        this.row = params.row || 1;
        this.col = params.column || 1;
        this.cb_host = params.cb_host;
        this.item_setter = params.item_setter;
        this.recycle_cb = params.recycle_cb;
        this.select_cb = params.select_cb;
        this.select_setter = params.select_setter;
        this.scroll_to_end_cb = params.scroll_to_end_cb;
        this.auto_scrolling = params.auto_scrolling || false;
        this.node_pool = [];

        if (this.dir == ListViewDir.Vertical) {
            let real_width: number = (this.item_width + this.gapX) * this.col - this.gapX;
            if (real_width > this.width) {
                cc.log("real width > width!!! resize scrollview to realwidth!! ", this.width, "--->>", real_width);
                this.width = real_width;
            }
            this.content.width = this.width;
        }else{
            let real_height: number = (this.item_height + this.gapY) * this.row - this.gapY;
            if (real_height > this.height) {
                cc.log("real height > height!!! resize scrollview to realheight!! ", this.height, "--->>", real_height);
                this.height = real_height;
            }
            this.content.height = this.height;
        }
        this.mask.setContentSize(this.width, this.height);
        this.mask.addComponent(cc.Mask);
        this.scrollview.node.setContentSize(this.width, this.height);
        this.scrollview.vertical = this.dir == ListViewDir.Vertical;
        this.scrollview.horizontal = this.dir == ListViewDir.Horizontal;
        this.scrollview.inertia = true;
        this.scrollview.node.on("scrolling", this.onScrolling, this);
        this.scrollview.node.on("scroll-to-bottom", this.onScrollToEnd, this);
        this.scrollview.node.on("scroll-to-right", this.onScrollToEnd, this);
    }

    private onScrolling(){
        if (!this.items || !this.items.length) {
            return;
        }
        if (this.dir == ListViewDir.Vertical) {
            let posy: number = this.content.y;
            if (posy < 0) {
                posy = 0;
            }
            if (posy > this.content.height - this.height) {
                posy = this.content.height - this.height;
            }
            let start: number = 0;
            let stop: number = this.items.length - 1;
            let viewport_start: number = -posy;
            let viewport_stop: number = viewport_start - this.height;
            while (this.items[start].y - this.item_height > viewport_start) {
                start++;
            }
            while (this.items[stop].y < viewport_stop) {
                stop--;
            }
            if (start != this.start_index && stop != this.stop_index) {
                this.start_index = start;
                this.stop_index = stop;
                this.render_items();
            }
        }else{
            let posx: number = this.content.x;
            if (posx > 0) {
                posx = 0;
            }
            if (posx < this.width - this.content.width) {
                posx = this.width - this.content.width;
            }
            let start: number = 0;
            let stop: number = this.items.length - 1;
            let viewport_start: number = -posx;
            let viewport_stop: number = viewport_start + this.width;
            while (this.items[start].x + this.item_width < viewport_start) {
                start++;
            }
            while (this.items[stop].x > viewport_stop) {
                stop--;
            }
            if (start != this.start_index && stop != this.stop_index) {
                this.start_index = start;
                this.stop_index = stop;
                this.render_items();
            }
        }
    }

    private render_items(){
        let item: ListItem;
        for (let i: number = 0; i < this.start_index; i++) {
            item = this.items[i];
            if (item.node) {
                this.recycle_item(item);
            }
        }
        for (let i: number = this.items.length; i > this.stop_index; i--) {
            item = this.items[i];
            if (item.node) {
                this.recycle_item(item);
            }
        }
        for (let i: number = this.start_index; i < this.stop_index; i++) {
            item = this.items[i];
            if (!item.node) {
                item.node = this.spawn_node(i);
                this.item_setter.call(this.cb_host, item.node, item.data, i);
                if (this.select_setter) {
                    this.select_setter.call(this.cb_host, item.node, item.is_select, i);
                }
            }
            item.node.setPosition(item.x, item.y);
        }
    }

    private recycle_item(item: ListItem){
        if (item.node && cc.isValid(item.node)) {
            if (this.recycle_cb) {
                this.recycle_cb.call(this.cb_host, item.node);
            }
            item.node.removeFromParent();
            this.node_pool.push(item.node);
            item.node = null;
        }
    }

    private spawn_node(idx: number): cc.Node{
        let node: cc.Node = this.node_pool.pop();
        if (!node) {
            node = cc.instantiate(this.item_tpl);
            node.active = true;
        }
        node.setParent(this.content);
        return node;
    }

    private onScrollToEnd(){
        if (this.scroll_to_end_cb) {
            this.scroll_to_end_cb.call(this.cb_host);
        }
    }
}